﻿/*
 * TextServices / TSDotNet / TSNative
 * Marco Maria Bellocchi, Mauro Bampo
 * Copyright(c) 2013.
 * 
 * The MIT License
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * 
 */

using System;
using System.Collections.Generic;

namespace TSDotNet.Encoders
{
    public interface IWordEncoder<B,C>
    {
        C Encode(B b);
        B Decode(C c);
        bool IsFillerByte(byte fillerByte);
        C EncodeFiller();
        C EncodeZeroPlaceholder();
        B DecodeZeroPlaceholder();
        void CheckIsValid();
    }

    public sealed class QuickEncoder : IWordEncoder<char, byte>
    {
        private byte[] _byteToChar = new byte[1024];
        private char[] _charToByte = new char[1024];

        public QuickEncoder()
        {
            _byteToChar[(int)'1'] = 1;
            _byteToChar[(int)'2'] = 2;
            _byteToChar[(int)'3'] = 3;
            _byteToChar[(int)'4'] = 4;
            _byteToChar[(int)'5'] = 5;
            _byteToChar[(int)'6'] = 6;
            _byteToChar[(int)'7'] = 7;
            _byteToChar[(int)'8'] = 8;
            _byteToChar[(int)'9'] = 9;
            _byteToChar[(int)'0'] = 10;
            _byteToChar[(int)'-'] = 11;
            _byteToChar[(int)','] = 15;
            _byteToChar[(int)'.'] = 12;
            _byteToChar[(int)'Z'] = 13;
            _byteToChar[(int)'$'] = 14;

            _charToByte[1] = '1';
            _charToByte[2] = '2';
            _charToByte[3] = '3';
            _charToByte[4] = '4';
            _charToByte[5] = '5';
            _charToByte[6] = '6';
            _charToByte[7] = '7';
            _charToByte[8] = '8';
            _charToByte[9] = '9';
            _charToByte[10] = '0';
            _charToByte[11] = '-';
            _charToByte[15] = ',';
            _charToByte[12] = '.';
            _charToByte[13] = 'Z';
            _charToByte[14] = '$';

        }
        
        
        public byte Encode(char b)
        {
            return _byteToChar[(int)b];
        }

        public char Decode(byte c)
        {
            return _charToByte[(int)c];
        }

        public bool IsFillerByte(byte fillerByte)
        {
            return fillerByte==15;
        }

        public byte EncodeFiller()
        {
            return 15;
        }

        public byte EncodeZeroPlaceholder()
        {
            return 14;
        }

        public char DecodeZeroPlaceholder()
        {
            return 'Z';
        }

        public void CheckIsValid()
        {
           
        }
    }

    public sealed class NumericStringEncoder : IWordEncoder<char,byte>
    {
        private readonly List<char> _reservedChar = new List<char> { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-','+','.' };
        private readonly List<byte> _bytesToUse = new List<byte> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 };
        private readonly char _numberSeparator;
        private readonly char _filler;
        private readonly char _zeroPlaceHolder;
        private readonly byte _numberSeparatorByte;
        private readonly byte _fillerByte;
        private readonly byte _zeroPlaceHolderByte;
        private IDictionary<char, byte> _encoder = new Dictionary<char, byte>();
        private IDictionary<byte, char> _decoder = new Dictionary<byte, char>();

        public NumericStringEncoder(
            char numberSeparator, byte numberSeparatorByte,
            char filler, byte fillerByte,
            char zeroPlaceHolder, byte zeroPlaceHolderByte)
        {
            if (numberSeparator == filler || numberSeparator == zeroPlaceHolder || filler == zeroPlaceHolder)
                throw new ArgumentException("numberSeparator == filler || numberSeparator==zeroPlaceHolder||filler==zeroPlaceHolder");
            if (numberSeparatorByte == fillerByte || numberSeparatorByte == zeroPlaceHolderByte || fillerByte == zeroPlaceHolderByte)
                throw new ArgumentException("numberSeparatorByte == fillerByte || numberSeparatorByte == zeroPlaceHolderByte || fillerByte == zeroPlaceHolderByte");
            if( _reservedChar.Contains(numberSeparator) ||
                _reservedChar.Contains(filler) ||
                _reservedChar.Contains(zeroPlaceHolder))
                throw new ArgumentException("_reservedChar.Contains(numberSeparator)  || _reservedChar.Contains(filler)||_reservedChar.Contains(zeroPlaceHolder)");
            if (!_bytesToUse.Contains(numberSeparatorByte))
                throw new ArgumentException("!_bytesToUse.Contains(numberSeparatorByte)");
            if (!_bytesToUse.Contains(fillerByte))
                throw new ArgumentException("!_bytesToUse.Contains(fillerByte)");
            if (!_bytesToUse.Contains(zeroPlaceHolderByte))
                throw new ArgumentException("!_bytesToUse.Contains(zeroPlaceHolderByte)");
            _numberSeparator = numberSeparator;
            _filler = filler;
            _zeroPlaceHolder = zeroPlaceHolder;
            _numberSeparatorByte = numberSeparatorByte;
            _fillerByte = fillerByte;
            _zeroPlaceHolderByte = zeroPlaceHolderByte;
            _bytesToUse.Remove(_numberSeparatorByte);
            _bytesToUse.Remove(_fillerByte);
            _bytesToUse.Remove(_zeroPlaceHolderByte);
            _encoder.Add(_numberSeparator, _numberSeparatorByte);
            _encoder.Add(_filler, _fillerByte);
            _encoder.Add(_zeroPlaceHolder, _zeroPlaceHolderByte);

            _decoder.Add(_numberSeparatorByte, _numberSeparator);
            _decoder.Add(_fillerByte, _filler);
            _decoder.Add(_zeroPlaceHolderByte, _zeroPlaceHolder);
        }

        public void AddEncoding(char c, byte b)
        {
            if (!_reservedChar.Contains(c))
                throw new ArgumentException("The char is not a numeric value");
            if (!_bytesToUse.Contains(b))
                throw new ArgumentException("The byte is not valid");
            if (_decoder.ContainsKey(b))
                throw new ArgumentException("The char is already contained!");
            if(_encoder.ContainsKey(c))
                throw new ArgumentException("The byte is already contained!");
            _encoder.Add(c, b);
            _decoder.Add(b, c);
        }

        public void CheckIsValid()
        {
            if (_decoder.Count != _bytesToUse.Count + 3)
                throw new ApplicationException("_decoder.Count == _bytesToUse.Count + 3");
        }

        public byte Encode(char b)
        {
            return _encoder[b];
        }

        public char Decode(byte b)
        {
            return _decoder[b];
        }

        public byte EncodeFiller()
        {
            return _encoder[_filler];
        }

        public byte EncodeZeroPlaceholder()
        {
            return _encoder[_zeroPlaceHolder];
        }

        public char DecodeZeroPlaceholder()
        {
            return _decoder[_zeroPlaceHolderByte];
        }

        public bool IsFillerByte(byte fillerByte)
        {
            return fillerByte == _fillerByte;
        }
    }
}
